package com.pig4cloud.pigx.contract.executer.order.matcher.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mongodb.client.result.UpdateResult;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.contract.constant.ContractMqConstants;
import com.pig4cloud.pigx.contract.entity.ContractOrder;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.executer.order.exception.OrderExecutorIgnoreException;
import com.pig4cloud.pigx.contract.executer.order.matcher.OrderMatcher;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractOrderMapper;
import com.pig4cloud.pigx.contract.mapstruct.ContractOrderStruct;
import com.pig4cloud.pigx.contract.mongo.entity.ContractOrderMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractTradeMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;

/**
 * 订单撮合器抽象实现
 *
 * @since 2022/8/17 11:17
 */
@Slf4j
@AllArgsConstructor
public abstract class AbstractOrderMatcher implements OrderMatcher {

    protected final ContractOrderMapper contractOrderMapper;

    protected final MongoTemplate mongo;
    protected final AmqpTemplate rabbit;

    protected final CommonUtil commonUtil;
    protected final ContractUtils contractUtils;


    /**
     * 获取订单信息
     *
     * @param id    id
     * @param isBuy 是否是买单
     * @return {@link ContractOrderMongo}
     * @since 2022/8/17 21:21
     */
    @Override
    public ContractOrderMongo getOrder(Long id, String isBuy) {
        if (Objects.isNull(id)) {
            throw new RuntimeException("数据异常，id为空");
        }
        if (Strings.isEmpty(isBuy)) {
            throw new RuntimeException("数据异常，isBuy为空");
        }

        String orderBookName = ContractUtils.getOrderBookName(isBuy);

        // 从mongodb中查询
        Criteria criteria = Criteria.where("id").is(id);
        ContractOrderMongo mongoOrder = mongo.findOne(Query.query(criteria),
                ContractOrderMongo.class,
                orderBookName);
        if (!Objects.isNull(mongoOrder)) {
            return mongoOrder;
        }

        // 从mysql中查询，委托中的订单
        QueryWrapper<ContractOrder> queryOrder = new QueryWrapper<>();
        queryOrder.lambda()
                .eq(ContractOrder::getId, id)
                .eq(ContractOrder::getStatus, ContractOrderEnum.Status.ON_GOING.getValue());
        ContractOrder mysqlOrder = contractOrderMapper.selectOne(queryOrder);

        if (Objects.isNull(mysqlOrder)) {
            throw new MqAckException("数据异常，未能获取到订单信息：" + id);
        }

        // 保存到mongodb
        mongoOrder = ContractOrderStruct.INSTANCE.entity2Mongo(mysqlOrder);
        mongoOrder.setHasAddPendingNum(true);
        mongoOrder.setIsConditionPending(ContractUtils.isConditionPending(mongoOrder.getHasStopProfit(), mongoOrder.getHasStopLost()));
        mongoOrder.setHasAddConditionPendingNum(mongoOrder.getIsConditionPending());
        mongo.save(mongoOrder, orderBookName);

        return mongoOrder;
    }

    /**
     * 撮合订单
     *
     * @param taker 订单
     * @return {@link ContractOrderMongo}
     * @since 2022/8/17 11:15
     */
    @Override
    public ContractOrderMongo match(ContractOrderMongo taker) {
        log.info("执行的Matcher：" + this.getClass().getSimpleName());
        // 查询最优订单薄
        List<ContractOrderMongo> orderBook = getOrderBook(taker);
        // 开始撮合
        for (int i = 0; i < orderBook.size(); i++) {
            ContractOrderMongo maker = orderBook.get(i);
            ContractTradeMongo makerTrade = null;

            try {
                // 判断是否能交易
                if (!matchable(taker, maker)) {
                    return taker;
                }

                // 获得可以撮合的数量，以数量更少的订单为准
                int compare = taker.getNoDealNum().compareTo(maker.getNoDealNum());
                BigDecimal dealNum = compare >= 0
                        ? maker.getNoDealNum()
                        : taker.getNoDealNum();

                // 撮合成功，首先初始化两个成交记录的id
                Long takerTradeId = commonUtil.getId();
                Long makerTradeId = commonUtil.getId();

                // 先处理订单薄订单
                makerTrade = handleMaker(taker, maker, dealNum, takerTradeId, makerTradeId);
                // 处理此订单
                handleTaker(taker, maker, dealNum, takerTradeId, makerTrade);

                // 根据内存中的数据刷新JVM中的taker
                taker = getOrder(taker.getId(), taker.getIsBuy());
                // 如果完全成交，结束循环
                if (taker.getNoDealNum().compareTo(BigDecimal.ZERO) == 0) {
                    break;
                }

                // 未完全成交，内存订单薄遍历完后，重新查询最优订单薄
                if (i == orderBook.size() - 1) {
                    orderBook = getOrderBook(taker);
                    i = -1;
                }

            } catch (Throwable e) {
                if (e instanceof OrderExecutorIgnoreException) {
                    // 如果maker的成交记录已经生成
                    if (!Objects.isNull(makerTrade)) {
                        // 回滚内存maker的数量
                        Update update = new Update();
                        update.inc("dealAmount", makerTrade.getTradeValue().negate())
                                .inc("dealNum", makerTrade.getNum().negate())
                                .inc("noDealAmount", makerTrade.getTradeValue())
                                .inc("noDealNum", makerTrade.getNum());

                        // maker修改条件
                        Criteria criteria = Criteria.where("id").is(maker.getId())
                                .and("dealNum").gte(makerTrade.getNum());
                        mongo.updateFirst(Query.query(criteria),
                                update,
                                ContractOrderMongo.class,
                                ContractUtils.getOrderBookName(maker.getIsBuy()));
                    }

                    // 根据内存中的数据刷新JVM中的taker
                    taker = getOrder(taker.getId(), taker.getIsBuy());
                    continue;
                }

                throw e;
            }
        }

        return taker;
    }

    /**
     * 处理此订单
     *
     * @param taker        新订单
     * @param maker        对手盘订单
     * @param dealNum      成交数量
     * @param takerTradeId 此订单成交记录id
     * @param makerTrade   对手订单成交记录
     * @since 2022/8/18 18:31
     */
    private void handleTaker(ContractOrderMongo taker,
                             ContractOrderMongo maker,
                             BigDecimal dealNum,
                             Long takerTradeId,
                             ContractTradeMongo makerTrade) {

        // 以maker的价格乘与dealNum
        BigDecimal dealAmount = maker.getPrice().multiply(dealNum);
        // taker修改参数
        Update update = new Update();
        update.inc("dealAmount", dealAmount)
                .inc("dealNum", dealNum)
                .inc("noDealAmount", dealAmount.negate())
                .inc("noDealNum", dealNum.negate());

        // taker修改条件
        Criteria criteria = Criteria.where("id").is(taker.getId())
                .and("noDealNum").gte(dealNum);
        UpdateResult updateResult = mongo.updateFirst(Query.query(criteria),
                update,
                ContractOrderMongo.class,
                ContractUtils.getOrderBookName(taker.getIsBuy()));
        // 如果修改失败
        if (updateResult.getModifiedCount() != 1) {
            throw new OrderExecutorIgnoreException();
        }

        // 如果maker已经完全成交，尝试删除mongo缓存
        if (maker.getNoDealNum().subtract(dealNum).compareTo(BigDecimal.ZERO) == 0) {
            Criteria removeCriteria = Criteria.where("id").is(taker.getId())
                    .and("noDealNum").is(BigDecimal.ZERO);
            Query removeMaker = Query.query(removeCriteria);
            mongo.remove(removeMaker,
                    ContractOrderMongo.class,
                    ContractUtils.getOrderBookName(maker.getIsBuy()));
        }

        // 组装成交记录
        ContractTradeMongo takerTrade = getTrade(taker, maker, maker.getPrice(), dealNum, takerTradeId, makerTrade.getId());
        // takerTrade存储到mongo
        mongo.save(takerTrade);
        // makerTrade存储到mongo
        mongo.save(makerTrade);
        // 发送新增成交记录mq消息
        sendTradeMq(takerTrade, makerTrade);
    }

    /**
     * 发送新增成交记录mq消息
     *
     * @param taker taker
     * @param maker maker
     * @since 2022/8/30 17:32
     */
    protected void sendTradeMq(ContractTradeMongo taker, ContractTradeMongo maker) {
        // 发送mq消息
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_TRADE_NEW,
                null,
                taker.getId());

        // 发送深度图参数消息。深度图功能暂时弃用该消息队列，2022.9.7（可通过该条注释检索到其它深度图消息发送代码位置）
		/*RobotDepthMapMqBO takerBO = new RobotDepthMapMqBO();
		takerBO.setSymbolName(taker.getSymbolName());
		takerBO.setSide(taker.getSide());
		takerBO.setNum(taker.getNum());
		takerBO.setPrice(taker.getPrice());
		rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_TRADE_NEW_DEPTH_MAP,
				null,
				takerBO);

		RobotDepthMapMqBO makerBO = new RobotDepthMapMqBO();
		makerBO.setSymbolName(maker.getSymbolName());
		makerBO.setSide(maker.getSide());
		makerBO.setNum(maker.getNum());
		makerBO.setPrice(maker.getPrice());
		rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_TRADE_NEW_DEPTH_MAP,
				null,
				makerBO);*/
    }

    /**
     * 处理maker
     *
     * @param taker        新订单
     * @param maker        对手盘订单
     * @param dealNum      成交数量
     * @param takerTradeId 此订单成交记录id
     * @param makerTradeId 对手订单成交记录id
     * @return {@link ContractTradeMongo}
     * @since 2022/8/18 17:26
     */
    private ContractTradeMongo handleMaker(ContractOrderMongo taker,
                                           ContractOrderMongo maker,
                                           BigDecimal dealNum,
                                           Long takerTradeId,
                                           Long makerTradeId) {

        BigDecimal dealAmount = maker.getPrice().multiply(dealNum);
        // 只修改非机器人数据
        if (maker.getIsRobot().equals(CommonDictEnum.YesOrNo.NO.getValue())) {
            // maker修改参数
            Update update = new Update();
            update.inc("dealAmount", dealAmount)
                    .inc("dealNum", dealNum)
                    .inc("noDealAmount", dealAmount.negate())
                    .inc("noDealNum", dealNum.negate());

            // maker修改条件
            Criteria criteria = Criteria.where("id").is(maker.getId())
                    .and("noDealNum").gte(dealNum);
            UpdateResult updateResult = mongo.updateFirst(Query.query(criteria),
                    update,
                    ContractOrderMongo.class,
                    ContractUtils.getOrderBookName(maker.getIsBuy()));
            // 如果修改失败
            if (updateResult.getModifiedCount() != 1) {
                throw new OrderExecutorIgnoreException();
            }
        }
        // 组装成交记录
        return getTrade(maker, taker, maker.getPrice(), dealNum, makerTradeId, takerTradeId);
    }

    /**
     * 获得成交记录mongo对象
     *
     * @param current         当前处理订单
     * @param opponent        对手订单
     * @param price           成交价格
     * @param dealNum         成交数量
     * @param currentTradeId  当前订单成交记录id
     * @param opponentTradeId 对手订单成交记录id
     * @return {@link ContractTradeMongo}
     * @since 2022/8/18 18:27
     */
    private ContractTradeMongo getTrade(ContractOrderMongo current,
                                        ContractOrderMongo opponent,
                                        BigDecimal price,
                                        BigDecimal dealNum,
                                        Long currentTradeId,
                                        Long opponentTradeId) {

        ContractTradeMongo trade = new ContractTradeMongo();
        trade.setId(currentTradeId);
        trade.setUserId(current.getUserId());
        trade.setSymbolId(current.getSymbolId());
        trade.setSymbolName(current.getSymbolName());
        trade.setOrderId(current.getId());
        trade.setOrderType(current.getType());
        trade.setMakerUserId(opponent.getUserId());
        trade.setMakerOrderId(opponent.getId());
        trade.setMakerTradeId(opponentTradeId);
        trade.setPositionId(current.getPositionId());
        trade.setIsBuy(current.getIsBuy());
        trade.setIsRobot(current.getIsRobot());
        trade.setSide(current.getSide());
        trade.setOperationType(current.getOperationType());
        trade.setPrice(price);
        trade.setNum(dealNum);
        trade.setTradeValue(OrderCalculator.getOrderValue(trade.getPrice(), trade.getNum()));
        trade.setMultiple(current.getMultiple());
        trade.setHandlingFee(BigDecimal.ZERO);
        trade.setHandlingFeeRate(BigDecimal.ZERO);
        trade.setMargin(BigDecimal.ZERO);
        trade.setProfit(BigDecimal.ZERO);
        trade.setLiquidateFee(BigDecimal.ZERO);
        trade.setLiquidateFeeRate(BigDecimal.ZERO);

        // 业务辅助字段
        trade.setPositionMode(current.getPositionMode());
        trade.setIsConditionPending(current.getIsConditionPending());
        trade.setOrderMargin(current.getMargin());
        trade.setOrderNum(current.getNum());

        return trade;
    }

    /**
     * 判断两个订单能否撮合
     *
     * @param taker 新订单
     * @param maker 对手盘订单
     * @return {@link boolean}
     * @since 2022/8/18 16:08
     */
    protected boolean matchable(ContractOrderMongo taker, ContractOrderMongo maker) {
        return taker.getNoDealNum().compareTo(BigDecimal.ZERO) > 0;
    }

    /**
     * 查询合适的对手盘
     *
     * @param taker 订单
     * @return {@link List<ContractOrderMongo>}
     * @since 2022/8/18 14:45
     */
    private List<ContractOrderMongo> getOrderBook(ContractOrderMongo taker) {
        // 查询反方向的订单薄
        String orderBookName = ContractUtils.getOpponentOrderBookName(taker.getIsBuy());
        Sort sort;
        // 买单
        if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), taker.getIsBuy())) {
            // 根据价格升序
            sort = Sort.by(Sort.Order.asc("price"));

        } else {
            // 卖单，根据价格降序
            sort = Sort.by(Sort.Order.desc("price"));
        }

        // 相同币对
        Criteria criteria = Criteria.where("symbolId").is(taker.getSymbolId())
                // 未交易数量 > 0
                .and("noDealNum").gt(0)
                // 不是新订单
                .and("isNew").is(false);

        // 设置额外筛选条件
        setExtraCriteria(criteria, taker);

        // 分页数据，查询前20条数据
        Query query = Query.query(criteria)
                .with(sort)
                .limit(20);

        // 查询
        return mongo.find(query, ContractOrderMongo.class, orderBookName);
    }

    /**
     * 查询订单薄时，设置额外筛选条件
     *
     * @param criteria 查询条件封装
     * @param taker    订单
     * @return {@link Criteria}
     * @since 2022/8/18 15:41
     */
    protected abstract Criteria setExtraCriteria(Criteria criteria, ContractOrderMongo taker);

    /**
     * 撮合失败
     *
     * @param taker 订单
     * @param e     异常
     * @since 2022/8/17 11:31
     */
    @Override
    public void fail(ContractOrderMongo taker, Exception e) {
    }
}
